In [1]:
from alpaca.trading.client import TradingClient
from alpaca.trading.requests import GetAssetsRequest
from alpaca.trading.enums import AssetClass
from alpaca.data.historical import CryptoHistoricalDataClient
from alpaca.data.requests import CryptoBarsRequest
from alpaca_secrets import APCA_API_KEY_ID, APCA_API_SECRET_KEY
import pandas as pd
import numpy as np
import talib
from backtesting import Strategy
from backtesting import Backtest, Strategy
from backtesting.lib import crossover
from datetime import datetime, timedelta
import inspect
trading_client = TradingClient(APCA_API_KEY_ID, APCA_API_SECRET_KEY)
import multiprocessing as mp
mp.set_start_method("fork", force=True)
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/backtesting/_plotting.py:55: UserWarning: Jupyter Notebook detected. Setting Bokeh output to notebook. This may not work in Jupyter clients without JavaScript support, such as old IDEs. Reset with `backtesting.set_bokeh_output(notebook=False)`.
warnings.warn('Jupyter Notebook detected. '
Data Collection¶
In [2]:
# search for US equities
search_params = GetAssetsRequest(asset_class=AssetClass.US_EQUITY)
assets = trading_client.get_all_assets(search_params)
In [ ]:
def prepare_data(list_symbol, n_years = 5):
data_client = StockHistoricalDataClient(APCA_API_KEY_ID, APCA_API_SECRET_KEY)
end_date = datetime(2025,7,15)
start_date = end_date - timedelta(days=n_years*365)
bars_request = StockBarsRequest(
symbol_or_symbols=list_symbol,
timeframe=TimeFrame.Minute,
# timeframe=TimeFrame.Hour,
start=start_date,
end=end_date,
adjustment="all"
)
bars = data_client.get_stock_bars(bars_request).data
dfs = {}
for sym in list_symbol:
print(f"Processing {sym}")
try:
asset = trading_client.get_asset(sym)
print(f"{asset.symbol}: Tradable = {asset.tradable}")
except Exception as e:
print(f"{sym}: Error - {e}")
candle = bars.get(sym, None)
if candle is not None:
dfs[sym] = pd.DataFrame([{k: getattr(bar, k) for k in ['timestamp', 'open', 'high', 'low', 'close', 'volume', 'vwap']} for bar in candle])
df = dfs[sym][['timestamp', 'open', 'high', 'low', 'close', 'volume']].copy()
df.columns = ['Timestamp', 'Open', 'High', 'Low', 'Close', 'Volume']
df['Timestamp'] = pd.to_datetime(df['Timestamp'])
df.set_index('Timestamp', inplace=True)
dfs[sym] = df
return dfs
In [4]:
def prepare_crypto_data(list_symbol, n_years=1):
client = CryptoHistoricalDataClient(APCA_API_KEY_ID, APCA_API_SECRET_KEY)
end = datetime(2025, 7, 15)
start = end - timedelta(days=n_years*365)
crypto_data = {}
for symbol in list_symbol:
print(f"Fetching {symbol}...")
request = CryptoBarsRequest(
symbol_or_symbols=symbol,
start=start,
end=end,
timeframe=TimeFrame.Minute,
# timeframe=TimeFrame.Hour,
adjustment="all"
)
bars = client.get_crypto_bars(request).df
if bars.empty:
print(f"No data for {symbol}")
continue
df = bars[bars.index.get_level_values(0) == symbol].droplevel(0).copy()
df.index.name = "timestamp"
df = df.rename(columns=str.lower)
df = df.reset_index()
# Supondo que df tenha ['open', 'high', 'low', 'close', 'volume']
df = df[['timestamp', 'open', 'high', 'low', 'close', 'volume']].copy()
df.columns = ['Timestamp', 'Open', 'High', 'Low', 'Close', 'Volume']
df['Timestamp'] = pd.to_datetime(df['Timestamp'])
df.set_index('Timestamp', inplace=True)
crypto_data[symbol] = df
return crypto_data
In [5]:
def print_results(results):
print(f"Return [%]: {results['Return [%]']:.2f}")
print(f"Buy & Hold Return [%]: {results['Buy & Hold Return [%]']:.2f}")
print(f"Sharpe Ratio: {results['Sharpe Ratio']:.2f}")
print(f"# Trades: {results['_trades'].shape[0]}")
print(f"Win Rate: {results['Win Rate [%]']:.2f}%")
print(f"Max Drawdown [%]: {results['Max. Drawdown [%]']:.2f}")
print(f"Avg Trade Duration: {results['Avg. Trade Duration']}")
print(f"Best Trade [%]: {results['Best Trade [%]']:.2f}")
print(f"Worst Trade [%]: {results['Worst Trade [%]']:.2f}")
print("="*60)
ETFs Data¶
In [6]:
list_symbol_ = ["SPY","QQQ","IWM","DIA","XLF","XLK","GLD","IAU","TLT","HYG",]
etfs_close_data={}
for sym in list_symbol_:
etfs_close_data[sym] = pd.read_csv(
f"etfs_{sym.split()[0]}.csv", index_col=0, parse_dates=True)
# for download of data
# etfs_close_data = prepare_data(list_symbol_, n_years = 1)
etfs_close_data.keys()
Out[6]:
dict_keys(['SPY', 'QQQ', 'IWM', 'DIA', 'XLF', 'XLK', 'GLD', 'IAU', 'TLT', 'HYG'])
Equities Data¶
In [7]:
eqt_symbol_ = ["AAPL","MSFT","GOOG","META","TSLA"]
eqt_close_data={}
for sym in eqt_symbol_:
eqt_close_data[sym] = pd.read_csv(
f"eqt_{sym.split()[0]}.csv", index_col=0, parse_dates=True)
# for download of data
# eqt_close_data = prepare_data(list_symbol_, n_years = 1)
eqt_close_data.keys()
Out[7]:
dict_keys(['AAPL', 'MSFT', 'GOOG', 'META', 'TSLA'])
Crypto Data¶
In [8]:
crypto_symbols = ["BTC/USD", "ETH/USD", "SOL/USD", "XRP/USD"]
crypto_close_data={}
for sym in crypto_symbols:
crypto_close_data[sym] = pd.read_csv(
f"crypto_{sym.split('/')[0]}.csv", index_col=0, parse_dates=True)
# for download of data
# crypto_close_data = prepare_crypto_data(crypto_symbols, n_years=1)
crypto_close_data.keys()
Out[8]:
| Open | High | Low | Close | Volume | |
|---|---|---|---|---|---|
| Timestamp | |||||
| 2024-07-15 00:00:00+00:00 | 60856.4680 | 60856.4680 | 60820.9735 | 60820.9735 | 0.0 |
| 2024-07-15 00:02:00+00:00 | 60854.2450 | 60854.2450 | 60854.2450 | 60854.2450 | 0.0 |
| 2024-07-15 00:03:00+00:00 | 60868.9000 | 60868.9000 | 60868.9000 | 60868.9000 | 0.0 |
| 2024-07-15 00:05:00+00:00 | 60844.4855 | 60844.4855 | 60844.4855 | 60844.4855 | 0.0 |
| 2024-07-15 00:08:00+00:00 | 60809.6100 | 60809.6100 | 60776.0935 | 60776.0935 | 0.0 |
Trend strats¶
In [9]:
class MACrossover(Strategy):
short_window = 5
long_window = 60
buffer_pct = 0.00
def init(self):
close = self.data.Close
self.ma_short = self.I(lambda x: pd.Series(x).rolling(self.short_window).mean(), close, name='ma_short')
self.ma_long = self.I(lambda x: pd.Series(x).rolling(self.long_window).mean(), close, name='ma_long')
def next(self):
if len(self.ma_short) < 2 or len(self.ma_long) < 2:
return
price = self.data.Close[-1]
diff = self.ma_short[-1] - self.ma_long[-1]
# Aplica o buffer
if diff > self.buffer_pct * price and self.ma_short[-2] <= self.ma_long[-2]:
if self.position.is_short:
self.position.close()
if not self.position.is_long:
self.buy(size=10)
elif diff < -self.buffer_pct * price and self.ma_short[-2] >= self.ma_long[-2]:
if self.position.is_long:
self.position.close()
if not self.position.is_short:
self.sell(size=10)
In [ ]:
class MACrossoverADX(Strategy):
short_window = 5
long_window = 60
adx_threshold = 30
T_period = 14
def init(self):
close = self.data.Close
self.ma_short = self.I(lambda x: pd.Series(x).rolling(self.short_window).mean(), close, name='ma_short')
self.ma_long = self.I(lambda x: pd.Series(x).rolling(self.long_window).mean(), close, name='ma_long')
self.adx = self.I(lambda h, l, c: talib.ADX(h, l, c, timeperiod=self.T_period),
self.data.High, self.data.Low, self.data.Close,
name='adx')
def next(self):
if len(self.ma_short) < 2 or len(self.ma_long) < 2 or len(self.adx) < 2:
return
price = self.data.Close[-1]
adx = self.adx[-1]
if adx < self.adx_threshold:
return
cross_up = self.ma_short[-1] > self.ma_long[-1] and self.ma_short[-2] <= self.ma_long[-2]
cross_down = self.ma_short[-1] < self.ma_long[-1] and self.ma_short[-2] >= self.ma_long[-2]
if cross_up:
if self.position.is_short:
self.position.close()
if not self.position.is_long:
self.buy(size=10)
elif cross_down:
if self.position.is_long:
self.position.close()
if not self.position.is_short:
self.sell(size=10)
In [ ]:
class MACrossoverADXStopLoss(Strategy):
short_window = 5
long_window = 60
adx_threshold = 30
T_period = 14
stop_loss_pct = 0.01
def init(self):
close = self.data.Close
self.ma_short = self.I(lambda x: pd.Series(x).rolling(self.short_window).mean(), close, name='ma_short')
self.ma_long = self.I(lambda x: pd.Series(x).rolling(self.long_window).mean(), close, name='ma_long')
self.adx = self.I(lambda h, l, c: talib.ADX(h, l, c, timeperiod=self.T_period),
self.data.High, self.data.Low, self.data.Close,
name='adx')
def next(self):
if len(self.ma_short) < 2 or len(self.ma_long) < 2 or len(self.adx) < 2:
return
price = self.data.Close[-1]
adx = self.adx[-1]
# Stop loss dinâmico
if self.position and self.trades:
entry_price = self.trades[-1].entry_price
if self.position.is_long:
loss = (price - entry_price) / entry_price
if loss < -self.stop_loss_pct:
self.position.close()
return
elif self.position.is_short:
loss = (entry_price - price) / entry_price
if loss < -self.stop_loss_pct:
self.position.close()
return
if adx < self.adx_threshold:
return
cross_up = self.ma_short[-1] > self.ma_long[-1] and self.ma_short[-2] <= self.ma_long[-2]
cross_down = self.ma_short[-1] < self.ma_long[-1] and self.ma_short[-2] >= self.ma_long[-2]
if cross_up:
if self.position.is_short:
self.position.close()
if not self.position.is_long:
self.buy(size=10)
elif cross_down:
if self.position.is_long:
self.position.close()
if not self.position.is_short:
self.sell(size=10)
In [ ]:
class MACrossoverADXBufferedStopLoss(Strategy):
short_window = 5
long_window = 60
adx_threshold = 30
T_period = 14
buffer_pct = 0.001
stop_loss_pct = 0.01
def init(self):
close = self.data.Close
self.ma_short = self.I(lambda x: pd.Series(x).rolling(self.short_window).mean(), close, name='ma_short')
self.ma_long = self.I(lambda x: pd.Series(x).rolling(self.long_window).mean(), close, name='ma_long')
self.adx = self.I(lambda h, l, c: talib.ADX(h, l, c, timeperiod=self.T_period),
self.data.High, self.data.Low, self.data.Close,
name='adx')
def next(self):
if len(self.ma_short) < 2 or len(self.ma_long) < 2 or len(self.adx) < 2:
return
price = self.data.Close[-1]
adx = self.adx[-1]
diff = self.ma_short[-1] - self.ma_long[-1]
buffer = self.buffer_pct * price
# Stop loss dinâmico
if self.position and self.trades:
entry_price = self.trades[-1].entry_price
if self.position.is_long:
loss = (price - entry_price) / entry_price
if loss < -self.stop_loss_pct:
self.position.close()
return
elif self.position.is_short:
loss = (entry_price - price) / entry_price
if loss < -self.stop_loss_pct:
self.position.close()
return
cross_up = diff > buffer and self.ma_short[-2] <= self.ma_long[-2]
cross_down = diff < -buffer and self.ma_short[-2] >= self.ma_long[-2]
# ENTRY: cruzamento com ADX forte e diferença significativa
if not self.position:
if cross_up and adx > self.adx_threshold:
self.buy(size=10)
elif cross_down and adx > self.adx_threshold:
self.sell(size=10)
else:
reverse_cross_up = diff > buffer and self.ma_short[-2] <= self.ma_long[-2]
reverse_cross_down = diff < -buffer and self.ma_short[-2] >= self.ma_long[-2]
if self.position.is_long and (reverse_cross_down):
self.position.close()
elif self.position.is_short and (reverse_cross_up):
self.position.close()
In [ ]:
class MACrossoverADXTrendAware(Strategy):
short_window = 5
long_window = 60
adx_threshold = 30
T_period = 14
def init(self):
close = self.data.Close
self.ma_short = self.I(lambda x: pd.Series(x).rolling(self.short_window).mean(), close, name='ma_short')
self.ma_long = self.I(lambda x: pd.Series(x).rolling(self.long_window).mean(), close, name='ma_long')
self.adx = self.I(lambda h, l, c: talib.ADX(h, l, c, timeperiod=self.T_period),
self.data.High, self.data.Low, self.data.Close,
name='adx')
def next(self):
if len(self.ma_short) < 2 or len(self.ma_long) < 2 or len(self.adx) < 2:
return
price = self.data.Close[-1]
adx = self.adx[-1]
cross_up = self.ma_short[-1] > self.ma_long[-1] and self.ma_short[-2] <= self.ma_long[-2]
cross_down = self.ma_short[-1] < self.ma_long[-1] and self.ma_short[-2] >= self.ma_long[-2]
if not self.position:
if cross_up and adx > self.adx_threshold:
self.buy(size=10)
elif cross_down and adx > self.adx_threshold:
self.sell(size=10)
else:
exit_condition = adx < self.adx_threshold
if self.position.is_long:
if exit_condition or cross_down:
self.position.close()
elif self.position.is_short:
if exit_condition or cross_up:
self.position.close()
In [ ]:
class MACrossoverADXBuffered(Strategy):
short_window = 20
long_window = 50
adx_threshold = 20
buffer_pct = 0.001
def init(self):
close = self.data.Close
self.ma_short = self.I(lambda x: pd.Series(x).rolling(self.short_window).mean(), close, name='ma_short')
self.ma_long = self.I(lambda x: pd.Series(x).rolling(self.long_window).mean(), close, name='ma_long')
self.adx = self.I(lambda h, l, c: talib.ADX(h, l, c, timeperiod=14),
self.data.High, self.data.Low, self.data.Close,
name='adx')
def next(self):
if len(self.ma_short) < 2 or len(self.ma_long) < 2 or len(self.adx) < 2:
return
price = self.data.Close[-1]
adx = self.adx[-1]
diff = self.ma_short[-1] - self.ma_long[-1]
buffer = self.buffer_pct * price
cross_up = diff > buffer and self.ma_short[-2] <= self.ma_long[-2]
cross_down = diff < -buffer and self.ma_short[-2] >= self.ma_long[-2]
if not self.position:
if cross_up and adx > self.adx_threshold:
self.buy(size=10)
elif cross_down and adx > self.adx_threshold:
self.sell(size=10)
else:
exit_due_to_adx = adx < self.adx_threshold
reverse_cross_up = diff > buffer and self.ma_short[-2] <= self.ma_long[-2]
reverse_cross_down = diff < -buffer and self.ma_short[-2] >= self.ma_long[-2]
if self.position.is_long and (exit_due_to_adx or reverse_cross_down):
self.position.close()
elif self.position.is_short and (exit_due_to_adx or reverse_cross_up):
self.position.close()
In [15]:
class TripleMACrossoverADXBuffered(Strategy):
short_window = 5
mid_window = 60
long_window = 120
adx_threshold = 30
buffer_pct = 0.001
def init(self):
close = self.data.Close
self.ma_short = self.I(lambda x: pd.Series(x).rolling(self.short_window).mean(), close, name='ma_short')
self.ma_mid = self.I(lambda x: pd.Series(x).rolling(self.mid_window).mean(), close, name='ma_mid')
self.ma_long = self.I(lambda x: pd.Series(x).rolling(self.long_window).mean(), close, name='ma_long')
self.adx = self.I(lambda h, l, c: talib.ADX(h, l, c, timeperiod=14),
self.data.High, self.data.Low, self.data.Close,
name='adx')
def next(self):
if len(self.ma_short) < 2 or len(self.ma_mid) < 2 or len(self.ma_long) < 2 or len(self.adx) < 2:
return
price = self.data.Close[-1]
adx = self.adx[-1]
buffer = self.buffer_pct * price
s, m, l = self.ma_short[-1], self.ma_mid[-1], self.ma_long[-1]
s_prev, m_prev, l_prev = self.ma_short[-2], self.ma_mid[-2], self.ma_long[-2]
aligned_up = s > m + buffer and m > l + buffer
aligned_down = s < m - buffer and m < l - buffer
was_aligned_up = s_prev > m_prev and m_prev > l_prev
was_aligned_down = s_prev < m_prev and m_prev < l_prev
if not self.position and adx > self.adx_threshold:
if aligned_up:
self.buy(size=10)
elif aligned_down:
self.sell(size=10)
elif self.position:
not_aligned = not (aligned_up or aligned_down)
adx_weak = adx < self.adx_threshold
if self.position.is_long and (not_aligned or adx_weak):
self.position.close()
elif self.position.is_short and (not_aligned or adx_weak):
self.position.close()
In [ ]:
class DualBreakoutStrategy(Strategy):
# breakout_window = 240
# exit_window = 30
breakout_window = 200
exit_window = 50
def init(self):
close = self.data.Close
self.high_100 = self.I(lambda x: pd.Series(x).rolling(self.breakout_window).max(), close, name='high_100')
self.low_100 = self.I(lambda x: pd.Series(x).rolling(self.breakout_window).min(), close, name='low_100')
self.high_50 = self.I(lambda x: pd.Series(x).rolling(self.exit_window).max(), close, name='high_50')
self.low_50 = self.I(lambda x: pd.Series(x).rolling(self.exit_window).min(), close, name='low_50')
def next(self):
if len(self.data.Close) < self.breakout_window:
return
close = self.data.Close[-1]
if self.position.is_long and close <= self.low_50[-1]:
self.position.close()
elif self.position.is_short and close >= self.high_50[-1]:
self.position.close()
if not self.position:
if close >= self.high_100[-1]:
self.buy(size=10)
elif close <= self.low_100[-1]:
self.sell(size=10)
In [ ]:
class AlwaysLongShort(Strategy):
short_window = 10
long_window = 100
buffer_pct = 0.000
def init(self):
close = self.data.Close
self.ma_short = self.I(lambda x: pd.Series(x).rolling(self.short_window).mean(), close, name='ma_short')
self.ma_long = self.I(lambda x: pd.Series(x).rolling(self.long_window).mean(), close, name='ma_long')
def next(self):
if len(self.ma_short) < 2 or len(self.ma_long) < 2:
return
price = self.data.Close[-1]
diff = abs(self.ma_short[-1] - self.ma_long[-1])
buffer = self.buffer_pct * price
crossed_up = self.ma_short[-2] <= self.ma_long[-2] and self.ma_short[-1] > self.ma_long[-1]
crossed_down = self.ma_short[-2] >= self.ma_long[-2] and self.ma_short[-1] < self.ma_long[-1]
if crossed_up:
if self.position.is_short:
self.position.close()
if not self.position.is_long and diff > buffer:
self.buy(size=10)
elif crossed_down:
if self.position.is_long:
self.position.close()
if not self.position.is_short and diff > buffer:
self.sell(size=10)
Backtesting¶
In [ ]:
def run_strategies(df, strategies, commission = 0.02, plots=False):
df_bt = df.reset_index()[['Timestamp', 'Open', 'High', 'Low', 'Close', 'Volume']].copy()
df_bt['Timestamp'] = pd.to_datetime(df_bt['Timestamp'])
df_bt.set_index('Timestamp', inplace=True)
for strategy in strategies:
print("="*60)
print(f"Running strategy: {strategy.__name__}")
print("-"*60)
bt = Backtest(df_bt, strategy, cash=100_000, commission=.01, exclusive_orders=True)
results = bt.run()
if plots:
bt.plot()
if 'all_results' not in locals():
all_results = []
result_dict = {
"Strategy": strategy.__name__,
"Return [%]": results['Return [%]'],
"Buy & Hold Return [%]": results['Buy & Hold Return [%]'],
"Sharpe Ratio": results['Sharpe Ratio'],
"# Trades": results['_trades'].shape[0],
"Win Rate [%]": results['Win Rate [%]'],
"Max Drawdown [%]": results['Max. Drawdown [%]'],
"Avg Trade Duration": results['Avg. Trade Duration'],
"Best Trade [%]": results['Best Trade [%]'],
"Worst Trade [%]": results['Worst Trade [%]'],
}
all_results.append(result_dict)
results_df = pd.DataFrame(all_results)
return results_df
Running strats on simple parameters¶
In [19]:
df = eqt_close_data["TSLA"]
df
strategies = [MACrossover,MACrossoverADX , MACrossoverADXStopLoss, MACrossoverADXBufferedStopLoss,
MACrossoverADXTrendAware, MACrossoverADXBuffered, TripleMACrossoverADXBuffered,
DualBreakoutStrategy, AlwaysLongShort]
all_results = run_strategies(df, strategies, commission =0)
============================================================ Running strategy: MACrossover ------------------------------------------------------------ ============================================================ Running strategy: MACrossoverADX ------------------------------------------------------------ ============================================================ Running strategy: MACrossoverADXStopLoss ------------------------------------------------------------ ============================================================ Running strategy: MACrossoverADXBufferedStopLoss ------------------------------------------------------------ ============================================================ Running strategy: MACrossoverADXTrendAware ------------------------------------------------------------ ============================================================ Running strategy: MACrossoverADXBuffered ------------------------------------------------------------ ============================================================ Running strategy: TripleMACrossoverADXBuffered ------------------------------------------------------------ ============================================================ Running strategy: DualBreakoutStrategy ------------------------------------------------------------ ============================================================ Running strategy: AlwaysLongShort ------------------------------------------------------------
Selection best strats on simple parameters¶
In [20]:
all_results.sort_values(by='Return [%]', ascending=False).head(3)
Out[20]:
| Strategy | Return [%] | Buy & Hold Return [%] | Sharpe Ratio | # Trades | Win Rate [%] | Max Drawdown [%] | Avg Trade Duration | Best Trade [%] | Worst Trade [%] | |
|---|---|---|---|---|---|---|---|---|---|---|
| 5 | MACrossoverADXBuffered | -1.078312 | 21.869577 | -3.154517 | 14 | 35.714286 | -1.083328 | 0 days 00:27:00 | 1.047751 | -2.267935 |
| 3 | MACrossoverADXBufferedStopLoss | -3.390940 | 21.888379 | -3.622008 | 66 | 24.242424 | -3.481877 | 0 days 21:41:00 | 33.300254 | -3.844950 |
| 1 | MACrossoverADX | -26.786983 | 21.888379 | -11.153592 | 482 | 34.647303 | -26.857234 | 0 days 18:08:00 | 23.523560 | -12.919924 |
In [21]:
all_results.sort_values(by='Win Rate [%]', ascending=False).head(3)
Out[21]:
| Strategy | Return [%] | Buy & Hold Return [%] | Sharpe Ratio | # Trades | Win Rate [%] | Max Drawdown [%] | Avg Trade Duration | Best Trade [%] | Worst Trade [%] | |
|---|---|---|---|---|---|---|---|---|---|---|
| 7 | DualBreakoutStrategy | -75.541811 | 22.654764 | -39.542841 | 1348 | 41.320475 | -75.542411 | 0 days 03:11:00 | 16.007643 | -6.062500 |
| 4 | MACrossoverADXTrendAware | -30.388459 | 21.888379 | -21.376846 | 519 | 40.655106 | -30.388459 | 0 days 00:16:00 | 3.196572 | -5.153485 |
| 6 | TripleMACrossoverADXBuffered | -97.014128 | 21.935409 | -77.403575 | 1634 | 37.698898 | -97.014128 | 0 days 00:29:00 | 5.581647 | -7.158386 |
In [22]:
all_results.sort_values(by='Sharpe Ratio', ascending=False).head(3)
Out[22]:
| Strategy | Return [%] | Buy & Hold Return [%] | Sharpe Ratio | # Trades | Win Rate [%] | Max Drawdown [%] | Avg Trade Duration | Best Trade [%] | Worst Trade [%] | |
|---|---|---|---|---|---|---|---|---|---|---|
| 5 | MACrossoverADXBuffered | -1.078312 | 21.869577 | -3.154517 | 14 | 35.714286 | -1.083328 | 0 days 00:27:00 | 1.047751 | -2.267935 |
| 3 | MACrossoverADXBufferedStopLoss | -3.390940 | 21.888379 | -3.622008 | 66 | 24.242424 | -3.481877 | 0 days 21:41:00 | 33.300254 | -3.844950 |
| 1 | MACrossoverADX | -26.786983 | 21.888379 | -11.153592 | 482 | 34.647303 | -26.857234 | 0 days 18:08:00 | 23.523560 | -12.919924 |
optmizing parameters for each top strats¶
MACrossoverADXBuffered¶
In [24]:
bt = Backtest(df, MACrossoverADXBuffered, cash=100_000, commission=.00, exclusive_orders=True)
results = bt.optimize(
short_window=range(5, 30, 5),
long_window=range(40, 200, 20),
adx_threshold=[25,30,35],
buffer_pct=[0.001, 0.005, 0.01],
maximize='Sharpe Ratio',
constraint=lambda p: p.short_window < p.long_window
)
print(results._strategy)
print_results(results)
bt.plot()
# MACrossoverADXBuffered(short_window=15,long_window=140,adx_threshold=30,buffer_pct=0.001)
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/backtesting/backtesting.py:1606: UserWarning: Searching for best of 360 configurations. output = _optimize_grid()
MACrossoverADXBuffered(short_window=15,long_window=140,adx_threshold=30,buffer_pct=0.001) Return [%]: 0.51 Buy & Hold Return [%]: 21.78 Sharpe Ratio: 2.30 # Trades: 24 Win Rate: 75.00% Max Drawdown [%]: -0.25 Avg Trade Duration: 0 days 00:39:00 Best Trade [%]: 3.73 Worst Trade [%]: -1.84 ============================================================
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/backtesting/_plotting.py:141: UserWarning: Data contains too many candlesticks to plot; downsampling to '1h'. See `Backtest.plot(resample=...)`
warnings.warn(f"Data contains too many candlesticks to plot; downsampling to {freq!r}. "
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/bokeh/util/serialization.py:242: UserWarning: no explicit representation of timezones available for np.datetime64
return convert(array.astype("datetime64[us]"))
Out[24]:
GridPlot(
id = 'p1398', …)
In [25]:
bt = Backtest(df, MACrossoverADXBuffered, cash=100_000, commission=.00, exclusive_orders=True)
results = bt.optimize(
short_window=range(5, 30, 5),
long_window=range(40, 200, 20),
adx_threshold=[25,30,35],
buffer_pct=[0.001, 0.005, 0.01],
maximize='Win Rate [%]',
constraint=lambda p: p.short_window < p.long_window
)
print(results._strategy)
print_results(results)
bt.plot()
# MACrossoverADXBuffered(short_window=5,long_window=60,adx_threshold=30,buffer_pct=0.005)
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/backtesting/backtesting.py:1606: UserWarning: Searching for best of 360 configurations. output = _optimize_grid()
MACrossoverADXBuffered(short_window=5,long_window=60,adx_threshold=30,buffer_pct=0.005) Return [%]: 0.15 Buy & Hold Return [%]: 21.89 Sharpe Ratio: 1.71 # Trades: 4 Win Rate: 100.00% Max Drawdown [%]: -0.06 Avg Trade Duration: 0 days 00:39:00 Best Trade [%]: 3.20 Worst Trade [%]: 0.77 ============================================================
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/backtesting/_plotting.py:141: UserWarning: Data contains too many candlesticks to plot; downsampling to '1h'. See `Backtest.plot(resample=...)`
warnings.warn(f"Data contains too many candlesticks to plot; downsampling to {freq!r}. "
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/bokeh/util/serialization.py:242: UserWarning: no explicit representation of timezones available for np.datetime64
return convert(array.astype("datetime64[us]"))
Out[25]:
GridPlot(
id = 'p1825', …)
MACrossoverADXBufferedStopLoss¶
In [26]:
bt = Backtest(df, MACrossoverADXBufferedStopLoss,
cash=100_000, commission=.00, exclusive_orders=True)
results = bt.optimize(
short_window=range(5, 30, 5),
long_window=range(80, 200, 20),
adx_threshold=[25,30,35],
buffer_pct=[0.001, 0.005, 0.01],
stop_loss_pct=[0.01,0.05],
maximize='Sharpe Ratio',
constraint=lambda p: p.short_window < p.long_window
)
print(results._strategy)
print_results(results)
bt.plot()
# MACrossoverADXBufferedStopLoss(short_window=5,long_window=120,adx_threshold=25,buffer_pct=0.005,stop_loss_pct=0.05)
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/backtesting/backtesting.py:1606: UserWarning: Searching for best of 540 configurations. output = _optimize_grid()
MACrossoverADXBufferedStopLoss(short_window=5,long_window=120,adx_threshold=25,buffer_pct=0.005,stop_loss_pct=0.05) Return [%]: 1.75 Buy & Hold Return [%]: 21.94 Sharpe Ratio: 1.20 # Trades: 4 Win Rate: 75.00% Max Drawdown [%]: -1.20 Avg Trade Duration: 28 days 08:55:00 Best Trade [%]: 69.66 Worst Trade [%]: -5.13 ============================================================
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/backtesting/_plotting.py:141: UserWarning: Data contains too many candlesticks to plot; downsampling to '1h'. See `Backtest.plot(resample=...)`
warnings.warn(f"Data contains too many candlesticks to plot; downsampling to {freq!r}. "
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/bokeh/util/serialization.py:242: UserWarning: no explicit representation of timezones available for np.datetime64
return convert(array.astype("datetime64[us]"))
Out[26]:
GridPlot(
id = 'p2252', …)
In [27]:
bt = Backtest(df, MACrossoverADXBufferedStopLoss,
cash=100_000, commission=.00, exclusive_orders=True)
results = bt.optimize(
short_window=range(5, 30, 5),
long_window=range(80, 200, 20),
adx_threshold=[25,30,35],
buffer_pct=[0.001, 0.005, 0.01],
stop_loss_pct=[0.01,0.05],
maximize='Win Rate [%]',
constraint=lambda p: p.short_window < p.long_window
)
print(results._strategy)
print_results(results)
bt.plot()
# MACrossoverADXBufferedStopLoss(short_window=5,long_window=80,adx_threshold=25,buffer_pct=0.01,stop_loss_pct=0.05)
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/backtesting/backtesting.py:1606: UserWarning: Searching for best of 540 configurations. output = _optimize_grid()
MACrossoverADXBufferedStopLoss(short_window=5,long_window=80,adx_threshold=25,buffer_pct=0.01,stop_loss_pct=0.05) Return [%]: 0.40 Buy & Hold Return [%]: 21.46 Sharpe Ratio: 0.88 # Trades: 1 Win Rate: 100.00% Max Drawdown [%]: -0.34 Avg Trade Duration: 6 days 11:31:00 Best Trade [%]: 14.76 Worst Trade [%]: 14.76 ============================================================
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/backtesting/_plotting.py:141: UserWarning: Data contains too many candlesticks to plot; downsampling to '1h'. See `Backtest.plot(resample=...)`
warnings.warn(f"Data contains too many candlesticks to plot; downsampling to {freq!r}. "
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/bokeh/util/serialization.py:242: UserWarning: no explicit representation of timezones available for np.datetime64
return convert(array.astype("datetime64[us]"))
Out[27]:
GridPlot(
id = 'p2679', …)
DualBreakoutStrategy¶
In [28]:
bt = Backtest(df, DualBreakoutStrategy,
cash=100_000, commission=.00, exclusive_orders=True)
results = bt.optimize(
breakout_window=range(60, 300, 60),
exit_window=range(30, 150, 30),
maximize='Sharpe Ratio',
)
print(results._strategy)
print_results(results)
bt.plot()
# DualBreakoutStrategy(breakout_window=240,exit_window=30)
DualBreakoutStrategy(breakout_window=240,exit_window=30) Return [%]: 4.47 Buy & Hold Return [%]: 22.11 Sharpe Ratio: 3.01 # Trades: 1484 Win Rate: 42.99% Max Drawdown [%]: -0.49 Avg Trade Duration: 0 days 01:55:00 Best Trade [%]: 11.79 Worst Trade [%]: -6.06 ============================================================
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/backtesting/_plotting.py:141: UserWarning: Data contains too many candlesticks to plot; downsampling to '1h'. See `Backtest.plot(resample=...)`
warnings.warn(f"Data contains too many candlesticks to plot; downsampling to {freq!r}. "
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/bokeh/util/serialization.py:242: UserWarning: no explicit representation of timezones available for np.datetime64
return convert(array.astype("datetime64[us]"))
Out[28]:
GridPlot(
id = 'p3077', …)
In [41]:
bt = Backtest(df, DualBreakoutStrategy,
cash=100_000, commission=.00, exclusive_orders=True)
results = bt.optimize(
breakout_window=range(60, 300, 60),
exit_window=range(30, 150, 30),
maximize='Win Rate [%]',
)
print(results._strategy)
print_results(results)
bt.plot()
# DualBreakoutStrategy(breakout_window=240,exit_window=30)
DualBreakoutStrategy(breakout_window=240,exit_window=30) Return [%]: 4.47 Buy & Hold Return [%]: 22.11 Sharpe Ratio: 3.01 # Trades: 1484 Win Rate: 42.99% Max Drawdown [%]: -0.49 Avg Trade Duration: 0 days 01:55:00 Best Trade [%]: 11.79 Worst Trade [%]: -6.06 ============================================================
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/backtesting/_plotting.py:141: UserWarning: Data contains too many candlesticks to plot; downsampling to '1h'. See `Backtest.plot(resample=...)`
warnings.warn(f"Data contains too many candlesticks to plot; downsampling to {freq!r}. "
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/bokeh/util/serialization.py:242: UserWarning: no explicit representation of timezones available for np.datetime64
return convert(array.astype("datetime64[us]"))
Out[41]:
GridPlot(
id = 'p3476', …)
Summary¶
In [37]:
data = [
["MACrossoverADXBuffered", "short=15, long=140, adx=30, buffer=0.001", 0.51, 21.78, 2.30, 24, 75.00, -0.25, "0 days 00:39:00", 3.73, -1.84],
["MACrossoverADXBuffered", "short=5, long=60, adx=30, buffer=0.005", 0.15, 21.89, 1.71, 4, 100.00, -0.06, "0 days 00:39:00", 3.20, 0.77],
["MACrossoverADXBufferedStopLoss", "short=5, long=120, adx=25, buffer=0.005, sl=0.05", 1.75, 21.94, 1.20, 4, 75.00, -1.20, "28 days 08:55:00", 69.66, -5.13],
["MACrossoverADXBufferedStopLoss", "short=5, long=80, adx=25, buffer=0.01, sl=0.05", 0.40, 21.46, 0.88, 1, 100.00, -0.34, "6 days 11:31:00", 14.76, 14.76],
["DualBreakoutStrategy", "breakout=240, exit=30", 4.47, 22.11, 3.01, 1484, 42.99, -0.49, "0 days 01:55:00", 11.79, -6.06],
["DualBreakoutStrategy", "breakout=240, exit=30", 4.47, 22.11, 3.01, 1484, 42.99, -0.49, "0 days 01:55:00", 11.79, -6.06]
]
columns = [
"Strategy", "Params", "Return [%]", "Buy & Hold Return [%]", "Sharpe Ratio",
"# Trades", "Win Rate [%]", "Max Drawdown [%]", "Avg Trade Duration",
"Best Trade [%]", "Worst Trade [%]"
]
summary = pd.DataFrame(data, columns=columns)
summary
Out[37]:
| Strategy | Params | Return [%] | Buy & Hold Return [%] | Sharpe Ratio | # Trades | Win Rate [%] | Max Drawdown [%] | Avg Trade Duration | Best Trade [%] | Worst Trade [%] | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | MACrossoverADXBuffered | short=15, long=140, adx=30, buffer=0.001 | 0.51 | 21.78 | 2.30 | 24 | 75.00 | -0.25 | 0 days 00:39:00 | 3.73 | -1.84 |
| 1 | MACrossoverADXBuffered | short=5, long=60, adx=30, buffer=0.005 | 0.15 | 21.89 | 1.71 | 4 | 100.00 | -0.06 | 0 days 00:39:00 | 3.20 | 0.77 |
| 2 | MACrossoverADXBufferedStopLoss | short=5, long=120, adx=25, buffer=0.005, sl=0.05 | 1.75 | 21.94 | 1.20 | 4 | 75.00 | -1.20 | 28 days 08:55:00 | 69.66 | -5.13 |
| 3 | MACrossoverADXBufferedStopLoss | short=5, long=80, adx=25, buffer=0.01, sl=0.05 | 0.40 | 21.46 | 0.88 | 1 | 100.00 | -0.34 | 6 days 11:31:00 | 14.76 | 14.76 |
| 4 | DualBreakoutStrategy | breakout=240, exit=30 | 4.47 | 22.11 | 3.01 | 1484 | 42.99 | -0.49 | 0 days 01:55:00 | 11.79 | -6.06 |
| 5 | DualBreakoutStrategy | breakout=240, exit=30 | 4.47 | 22.11 | 3.01 | 1484 | 42.99 | -0.49 | 0 days 01:55:00 | 11.79 | -6.06 |
In [45]:
bt = Backtest(df, DualBreakoutStrategy,
cash=100_000, commission=.00, exclusive_orders=True)
results = bt.run()
print(results._strategy)
print_results(results)
bt.plot()
# bt.plot(filename='final_image_mev_rev_iau.html')
# DualBreakoutStrategy(breakout_window=240,exit_window=30)
DualBreakoutStrategy Return [%]: 4.47 Buy & Hold Return [%]: 22.11 Sharpe Ratio: 3.01 # Trades: 1484 Win Rate: 42.99% Max Drawdown [%]: -0.49 Avg Trade Duration: 0 days 01:55:00 Best Trade [%]: 11.79 Worst Trade [%]: -6.06 ============================================================
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/backtesting/_plotting.py:141: UserWarning: Data contains too many candlesticks to plot; downsampling to '1h'. See `Backtest.plot(resample=...)`
warnings.warn(f"Data contains too many candlesticks to plot; downsampling to {freq!r}. "
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/bokeh/util/serialization.py:242: UserWarning: no explicit representation of timezones available for np.datetime64
return convert(array.astype("datetime64[us]"))
Out[45]:
GridPlot(
id = 'p4673', …)
In [40]:
summary.to_csv("strat_trend_summary.csv", index=False)
df_trend = pd.read_csv("strat_trend_summary.csv")
df_trend.to_latex("results_trend.csv_tex", index=False, longtable=True,escape=False)
In [ ]: